RMI
全称是Remote Method Invocation
,远程方法调用,从这个名字就可以看出,他的目标和RPC其实是类似的,是让某个Java虚拟机上的对象调⽤另⼀个Java虚拟机中对象上的⽅法,只不过RMI是Java独有的⼀种机制RMI Server:
package aaaa.rmi; import java.rmi.Naming; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.server.UnicastRemoteObject; public class RMIServer { public interface IRemoteHelloWorld extends Remote { public String hello() throws RemoteException; } public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld { protected RemoteHelloWorld() throws RemoteException { super(); } public String hello() throws RemoteException { System.out.println("call from"); return "Hello world"; } } private void start() throws Exception { RemoteHelloWorld h = new RemoteHelloWorld(); LocateRegistry.createRegistry(1099); Naming.rebind("rmi://127.0.0.1:1099/Hello", h); } public static void main(String[] args) throws Exception { new RMIServer().start(); } }
⼀个
RMI Server
分为三部分:- ⼀个继承了
java.rmi.Remote
的接⼝,其中定义我们要远程调⽤的函数,比如这里的hello()
- ⼀个实现了此接⼝的类
- ⼀个主类,⽤来创建
Registry
,并将上⾯的类实例化后绑定到⼀个地址,这就是所谓的Server了
- ⼀个继承了
RMI Client:
package aaaa.rmi; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; public class RMIClient { public static void main(String[] args) throws Exception { RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://192.168.128.178:1099/Hello"); String ret = hello.hello(); System.out.println( ret); } }
使⽤
Naming.lookup
在Registry
中寻找到名字是Hello的对象,后面的使用就和在本地使用⼀样了一个RMI过程有以下三个参与者:
RMI Registry
、RMI Server
、RMI Client
通常我们在新建一个
RMI Registry
的时候,都会直接绑定一个对象在上面,也就是说我们示例代码中的Server其实包含了Registry
和Server
两部分LocateRegistry.createRegistry(1099); //创建并运行RMI Registry Naming.bind("rmi://127.0.0.1:1099/Hello", new RemoteHelloWorld()); //行将RemoteHelloWorld对象绑定到Hello这个名字上
Naming.bind
的第一个参数是一个URL,形如:rmi://host:port/name
,其中,host
和port
就是RMI Registry
的地址和端口,name是远程对象的名字如果
RMI Registry
在本地运行,那么host
和port
是可以省略的,此时host
默认是localhost
,port
默认是1099
:Naming.bind("Hello", new RemoteHelloWorld());
攻击RMI Registry
Java对远程访问
RMI Registry
做了限制,只有来源地址是localhost
的时候,才能调用rebind
、bind
、unbind
等方法不过
list
和lookup
方法可以远程调用,list
方法可以列出目标上所有绑定的对象:String[] s = Naming.list("rmi://192.168.128.178:1099");
lookup
作用就是获得某个远程对象,那么,只要目标服务器上存在一些危险方法,通过RMI就可以对其进行调用
RMI利用codebase执行任意代码
Java Applet
是可以运行在浏览器中的,在使用Applet
的时候通常需要指定一个codebase
属性:<applet code="HelloWorld.class" codebase="Applets" width="800" height="600"> </applet>
除了
Applet
,RMI
中也存在远程加载的场景,也会涉及到codebase
,codebase
是一个地址,告诉Java虚拟机应该从哪个地方去搜索类,有点像CLASSPATH
,但CLASSPATH
是本地路径,而codebase
通常是远程URL,比如http
、ftp
等如果指定
codebase=http://example.com/
,然后加载org.vulhub.example.Example
类,则Java虚拟机会下载这个文件http://example.com/org/vulhub/example/Example.class
,并作为Example
类的字节码RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象,这些对象在反序列化时,就会去寻找类,如果某一端反序列化时发现一个对象,那么就会去自己的
CLASSPATH
下寻找想对应的类;如果在本地没有找到这个类,就会去远程加载codebase中的类如果
codebase
被控制,就可以加载恶意类了在RMI中,可以将
codebase
随着序列化数据一起传输,服务器在接收到这个数据后就会去CLASSPATH
和指定的codebase
寻找类,由于codebase
被控制导致任意命令执行漏洞漏洞条件:
- 安装并配置了
SecurityManager
- Java版本低于7u21、6u45,或者设置了
java.rmi.server.useCodebaseOnly=false
- 在
java.rmi.server.useCodebaseOnly
配置为 true 的情况下,Java虚拟机将只信任预先配置好的codebase
,不再支持从RMI请求中获取
- 安装并配置了
看着P神的文章想跟着复现一下不知道抽什么风一直报错,放弃了,我选择嗯看
研究一下他的源码,大概意思就是,先起一个
RMIServer
,在编译运行的时候设置三个参数:java.rmi.server.hostname=192.168.135.142 //是服务器的IP地址,远程调用时需要根据这个值来访问RMI Server java.rmi.server.useCodebaseOnly=false //支持从RMI请求中获取Codebase java.security.policy=client.policy //授权
再建立一个RMIClient.java,在另一个位置运行,此时
RMI Server
在本地CLASSPATH
里找不到类,会去加载Codebase
中的类import java.rmi.Naming; import java.util.List; import java.util.ArrayList; import java.io.Serializable; public class RMIClient implements Serializable { public class Payload extends ArrayList<Integer> {} public void lookup() throws Exception { ICalc r = (ICalc) Naming.lookup("rmi://192.168.135.142:1099/refObj"); List<Integer> li = new Payload(); li.add(3); li.add(4); System.out.println(r.sum(li)); } public static void main(String[] args) throws Exception { new RMIClient().lookup(); } }
查看
example.com
的日志,慧收到了来自Java的请求/RMIClient$Payload.class
,因为还没有实际放置这个类文件,所以会出现异常只需要编译一个恶意类,将其class文件放置在Web服务器的
/RMIClient$Payload.class
即可